// Copyright (c) 2013 The Foundry Visionmongers Ltd. All Rights Reserved.

#include "plugin.h"
#include "buildCamera.h"
#include "buildAccelerator.h"
#include "buildLight.h"
#include "buildRenderer.h"

// PBRT
#include <core/api.h>
#include <core/paramset.h>
#include <core/scene.h>


// KATANA
#include <RendererInfo/RenderMethod.h>
#include <RenderOutputUtils/RenderOutputUtils.h>
#include <Render/RenderSettings.h>
#include <FnScenegraphIterator/FnScenegraphIterator.h>

std::string PbrtRenderPlugin::hostAddress;
float PbrtRenderPlugin::frameTime;


PbrtRenderPlugin::PbrtRenderPlugin(
    FnKat::FnScenegraphIterator rootIterator,
    FnKat::GroupAttribute arguments)
    : FnKat::Render::RenderBase(rootIterator, arguments)
{
}

PbrtRenderPlugin::~PbrtRenderPlugin()
{
}

int PbrtRenderPlugin::start()
{
    try
    {
        Renderer *renderer = NULL;
        Scene *scene = NULL;

        // Initialize Parameters
        FnKat::FnScenegraphIterator rootIt = getRootIterator();
        FnKat::Render::RenderSettings renderSettings(rootIt);

        // Pbrt Options and Init
        Options options;
        options.quiet = false;
        options.nCores = 1; //TODO
        pbrtInit(options);

        // Basic Settings
        BasicSettings basicSettings;
        basicSettings.hostAddress = getKatanaHost();
        basicSettings.frameTime = getRenderTime();
        basicSettings.renderMethodName = getRenderMethodName();

        // Get renderer
        renderer = getRenderer(renderSettings, rootIt, &basicSettings);

        if (renderer != NULL)
        {
            // Create the scene
            scene = initScene( renderSettings );

            // Render the scene
            if (scene != NULL)
            {
                renderer->Render(scene);
            }
        }

        // Cleanup
        TasksCleanup();
        if (renderer != NULL) delete renderer;
        if (scene != NULL) delete scene;
        pbrtCleanup();

    }
    catch(const exception &e)
    {
        error("Error: rendering...");
        error("Exception reports '");
    }
    catch(...)
    {
        error("Error: rendering...");
        error("Unknown exception.");
    }

    return 0;
}

int PbrtRenderPlugin::pause()
{
    // Default:
    return FnKat::Render::RenderBase::pause();
}

int PbrtRenderPlugin::resume()
{
    // Default:
    return FnKat::Render::RenderBase::resume();
}

int PbrtRenderPlugin::stop()
{
    // Default:
    return FnKat::Render::RenderBase::stop();
}

int PbrtRenderPlugin::startLiveEditing()
{
    // Default:
    return FnKat::Render::RenderBase::startLiveEditing();
}

int PbrtRenderPlugin::stopLiveEditing()
{
    // Default:
    return FnKat::Render::RenderBase::stopLiveEditing();
}

int PbrtRenderPlugin::processControlCommand(const std::string& command)
{
    // Default:
    return FnKat::Render::RenderBase::processControlCommand(command);
}

int PbrtRenderPlugin::queueDataUpdates(FnKat::GroupAttribute updateAttribute)
{
    // Default:
    return FnKat::Render::RenderBase::queueDataUpdates(updateAttribute);
}

int PbrtRenderPlugin::applyPendingDataUpdates()
{
    // Default:
    return FnKat::Render::RenderBase::applyPendingDataUpdates();
}

bool PbrtRenderPlugin::hasPendingDataUpdates() const
{
    // Default:
    return FnKat::Render::RenderBase::hasPendingDataUpdates();
}

// Disk Render

void PbrtRenderPlugin::configureDiskRenderOutputProcess(
    FnKat::Render::DiskRenderOutputProcess& diskRenderOutputProcess,
    const std::string& outputName,
    const std::string& outputPath,
    const std::string& renderMethodName,
    const float& frameTime) const
{
    FnKat::FnScenegraphIterator rootIterator = getRootIterator();
    FnKat::Render::RenderSettings renderSettings(rootIterator);
    FnKat::Render::RenderSettings::RenderOutput output = renderSettings.getRenderOutputByName(outputName);

    // Generate a unique temporary local output location used by the renderer
    std::string tempRenderLocation = FnKat::RenderOutputUtils::buildTempRenderLocation(
                                         rootIterator, outputName, "render", "exr", frameTime);
    std::cout << "tempRenderLocation: " << tempRenderLocation << std::endl;
    // Use the path from the location plug-in as a final target location which is displayed in the monitor
    std::string targetRenderLocation = outputPath;

    // Declare the render action used for this render output
    std::auto_ptr<FnKat::Render::RenderAction> renderAction;

    // Determine the rendering behaviour based on the output type
    if (output.type == "color")
    {
        // Here we render to a temporary location and then convert and copy it to the final location
        renderAction.reset(new FnKat::Render::CopyAndConvertRenderAction(
              targetRenderLocation,
              tempRenderLocation,
              output.clampOutput,
              output.colorConvert,
              output.computeStats,
              output.convertSettings));
    }
    else
    {
        renderAction.reset( new FnKat::Render::NoOutputRenderAction() );
        std::cout << "Warning: Unknown render output type '" << output.type << "' for render output '" << outputName << "'." << std::endl;
    }

    diskRenderOutputProcess.setRenderAction( renderAction );
}

// Utils
//TODO: move to buildScene()
Scene *PbrtRenderPlugin::initScene( FnKat::Render::RenderSettings &renderSettings )
{
    FnKat::FnScenegraphIterator rootIt = getRootIterator();

    FnKat::FnScenegraphIterator worldIt = rootIt.getChildByName("world");
    if(!worldIt.isValid())
    {
        error("/root/world doesn't exist!");
        return NULL;
    }

    //TODO: add as a global option
    Aggregate *accelerator = getAccelerator(renderSettings, rootIt);

    //TODO: read from scenegraph
    VolumeRegion *volumeRegion = NULL;

    //Lights
    FnKat::GroupAttribute localLightListAttr = worldIt.getAttribute("lightList");
    FnKat::GroupAttribute globalLightListAttr = worldIt.getAttribute("lightList", true);
    vector<Light*>lights;

    if(localLightListAttr.isValid())
    {
        getLights(renderSettings, rootIt, localLightListAttr, lights);
    }
    else if(globalLightListAttr.isValid())
    {
        getLights(renderSettings, rootIt, globalLightListAttr, lights);
    }

    //TODO: check if lights can be empty, if not, add default lights?
    if(!localLightListAttr.isValid() && !globalLightListAttr.isValid())
    {
        error("No lightlist found!!");
    }

    Scene *scene = new Scene(accelerator, lights, volumeRegion);

    return scene;
}


DEFINE_RENDER_PLUGIN(PbrtRenderPlugin)


void registerPlugins()
{
    REGISTER_PLUGIN(PbrtRenderPlugin, "pbrt", 0, 1);
}
